# Name:    CFileRunnerGUI.py
# Date:    Tue Mar 23 22:26:28 CET 2010
# Purpose: main GUI class of fileRunner application
# Def:     CFileRunnerGUI()
# Inputs:

import os
import sys
import gtk
import gobject
import time
import errno
from threading import Lock

# my libraries
from CDataHnd import CDataHnd
from CTrayIcon import CTrayIcon
from CSimpleMsgDlg import CSimpleMsgDlg
from CAboutDlg import CAboutDlg
from CKeyPressHnd import CKeyPressHnd
from CProgressWndRnd import CProgressWndRnd
from CGenerateDb import CGenerateDb
from CExceptionHnd import CExceptionHnd
from CThread import CThread
import myconst

# my exceptions
from CProcMngr import CProcMngrStatusException
from CDataHnd import CDataHndDBFileNotExistExc

# http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show
gobject.threads_init()

####################################################################################################

class CFileRunnerGUI(gtk.Window, CExceptionHnd):
	__height = None

	__vbox = None
	__entry = None
	__completion = None
	__listStore = None
	__trayIcon = None

	__popupMenu = None
	__generateDbFileItem = None
	__aboutItem = None
	__exitItem = None

	__oDataHnd = None
	__oKeyPressHnd = None
	oProgBarWnd = None
	oDbGen = None

	__totalCntMsg = None
	__cleanEntry = None

	lock = Lock()

#---------------------------------------------------------------------------------------------------

	def __init__(self):
		super(CFileRunnerGUI, self).__init__()

		self.__oDataHnd = CDataHnd()
		self.__oDataHnd.setExterExcHndCB(self.handleException)

		self.__oKeyPressHnd = CKeyPressHnd()
		self.__cleanEntry = False

		# set member from CExceptionHnd
		self.setDoExit(False)

		# window settings
		self.set_position(gtk.WIN_POS_CENTER)
		self.set_title(myconst.LABEL)
		gtk.window_set_default_icon_from_file(myconst.ICON)

		self.__createPopupMenu()

		self.__trayIcon = CTrayIcon(myconst.ICON_TRAY, myconst.TOOLTIP, True)

		self.__createInputBox()

		# set window size
		(width, self.__height) = self.__entry.size_request()
		self.set_size_request(myconst.WIDTH, self.__height)

#---------------------------------------------------------------------------------------------------

	def __createInputBox(self):
		self.__vbox = gtk.VBox()
		self.__entry = gtk.Entry()
		self.__completion = gtk.EntryCompletion()
		self.__listStore = gtk.ListStore(gobject.TYPE_STRING)

		self.__vbox.pack_start(self.__entry)
		self.add(self.__vbox)
		self.__completion.set_text_column(0)
		self.__completion.set_match_func(self.__oDataHnd.match_func_C, self.__entry)
		#self.__completion.set_match_func(self.__oDataHnd.match_func_Py, self.__entry)

#---------------------------------------------------------------------------------------------------

	def __createPopupMenu(self):
		self.__popupMenu = gtk.Menu()
		
		self.__generateDbFileItem = gtk.MenuItem(myconst.MENU_GENERATE_DB_FILE)
		self.__aboutItem = gtk.MenuItem(myconst.MENU_ABOUT)
		self.__exitItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)

		self.__popupMenu.append(self.__generateDbFileItem)
		self.__popupMenu.append(self.__aboutItem)
		self.__popupMenu.append(self.__exitItem)

#---------------------------------------------------------------------------------------------------

	def __loadData(self):
		try:
			self.__oDataHnd.load()
			self.__loadInputBoxEntries()
		except:
			self.handle()

#---------------------------------------------------------------------------------------------------

	def __loadInputBoxEntries(self):
		i = 0
		self.__listStore.insert(i, [myconst.ENTRY_COMPLETION_HISTORY_SEP])
		i += 1
		# load all entries [from history file]
		self.__oDataHnd.oHistoryFile.begin()
		for s in self.__oDataHnd.oHistoryFile.getFileDesc():
			self.__listStore.insert(i, [s.replace('\n', '')])
			i += 1

		self.__listStore.insert(i, [myconst.ENTRY_COMPLETION_ALL_SEP])
		i += 1
		# load all entries [from DB file]
		self.__oDataHnd.oDBFile.begin()
		for s in self.__oDataHnd.oDBFile.getFileDesc():
			self.__listStore.insert(i, [s.replace('\n', '')])
			i += 1

		entriesCnt = len(self.__listStore) - myconst.ENTRY_COMPLETION_SEPARATOR_CNT
		self.__totalCntMsg = ' ' + str(entriesCnt) + myconst.MSG_ENTRY_FILES_LOADED
		print self.__totalCntMsg

		self.__completion.set_model(self.__listStore)
		self.__entry.set_completion(self.__completion)

		# set model for DataHnd - avoid calling get_model every time when match_func is called
		self.__oDataHnd.setModel(self.__completion.get_model())

#---------------------------------------------------------------------------------------------------

	def __cleanInputBoxEntries(self):
		self.__listStore = gtk.ListStore(gobject.TYPE_STRING)
		self.__completion.set_model(None)
		self.__entry.set_completion(self.__completion)
				
#---------------------------------------------------------------------------------------------------

	def __cleanEntryBox(self):
		self.__entry.set_text('')
				
#---------------------------------------------------------------------------------------------------

	def generateDB(self):
		try:
			self.oProgBarWnd = CProgressWndRnd(myconst.MSG_GENERATING_DB_FILE, self)
			# setting callback function
			self.oProgBarWnd.setCbFnc(self.loadData_cb)
			self.oProgBarWnd.start()

			self.oDbGen = CGenerateDb(self.__oDataHnd)
			self.oDbGen.setExterExcHndCB(self.handleException)
			# setting callback function
			self.oDbGen.setCbFnc(self.oProgBarWnd.stop)
			self.oDbGen.start()
		except: 
			self.handle()
			
#---------------------------------------------------------------------------------------------------

	# set the signals [callbacks]
	def __loadSignals(self):
		# main app [window] signals
		self.connect("delete-event", self.__hideWndSignal_cb)
		self.connect("key-press-event", self.__keyPressSignal_cb)

		# entry box signal - while inserting text
		self.__entry.connect('changed', self.__entryBoxChanged_cb)

		# entry completion list box signal
		self.__completion.connect('match-selected', self.__selectCompletionEntrySignal_cb)

		# menu items signals
		self.__generateDbFileItem.connect("activate", self.__regenerateDBFileSignal_cb)
		self.__aboutItem.connect("activate", self.__showAboutDlgSignal_cb)
		self.__exitItem.connect("activate", gtk.main_quit)

		# tray icon signals
		self.__trayIcon.connect("activate", self.__showWndSignal_cb)
		self.__trayIcon.connect("popup-menu", self.__popupMenuSignal_cb)

#---------------------------------------------------------------------------------------------------
# CALLBACKS
#---------------------------------------------------------------------------------------------------

	def loadData_cb(self):
		# do nothing if APP is going to exit, e.g. if getDoExit returns True
		if self.getDoExit() is False:
			self.__loadData() 
			if self.__totalCntMsg is not None:
				gobject.idle_add(self.__showMsg, myconst.MSG_FILEGENERATED_OK + str(self.__totalCntMsg))

#---------------------------------------------------------------------------------------------------

	def __hideWndSignal_cb(self, widget, event):
		self.__hideApp()
		return True

#---------------------------------------------------------------------------------------------------

	def __entryBoxChanged_cb(self, editable):
		self.__oDataHnd.prepareBeforeMatch(editable.get_text())

#---------------------------------------------------------------------------------------------------

	def __keyPressSignal_cb(self, widget, event):
		self.__oKeyPressHnd.setEvent(event)
		self.__oKeyPressHnd.processEvent()

#---------------------------------------------------------------------------------------------------

	def __popupMenuSignal_cb(self, status_icon, button, activate_time):
		self.__popupMenu.show_all()
		self.__popupMenu.popup(None, None, None, button, activate_time, None)

#---------------------------------------------------------------------------------------------------

	def __regenerateDBFileSignal_cb(self, widget):
		self.__cleanInputBoxEntries()
		self.__oDataHnd.load(False)
		self.generateDB()

#---------------------------------------------------------------------------------------------------

	def __selectCompletionEntrySignal_cb(self, completion, model, iter):
		if self.__oDataHnd.runFile(model[iter][0]) is False:
			# clean input expression box
			self.__cleanEntry = True
		
		self.__hideApp()

#---------------------------------------------------------------------------------------------------

	def __showWndSignal_cb(self, icon):
		if self.get_property('visible') is False:
			self.__showApp()
		else:
			self.__hideApp()

#---------------------------------------------------------------------------------------------------

	def __showAboutDlgSignal_cb(self, widget):
		self.set_skip_taskbar_hint(False)
		CAboutDlg()
		self.set_skip_taskbar_hint(True)

#---------------------------------------------------------------------------------------------------
# END OF CALLBACKS
#---------------------------------------------------------------------------------------------------

	def __hideApp(self):
		if self.oProgBarWnd is not None:
			self.oProgBarWnd.hideAll()
		self.hide_all()

#---------------------------------------------------------------------------------------------------

	def run(self):
		self.__showApp()
		self.__loadSignals()
		
		self.__loadData()
		gtk.main()

#---------------------------------------------------------------------------------------------------

	def __showApp(self):
		if self.__cleanEntry is True:
			self.__cleanEntryBox()
			self.__cleanEntry = False

		self.set_skip_taskbar_hint(False)
		if self.oProgBarWnd is not None:
			self.oProgBarWnd.showAll()
		self.show_all()
		self.set_skip_taskbar_hint(True)

#---------------------------------------------------------------------------------------------------

	def __showMsg(self, msg):
		self.set_skip_taskbar_hint(False)
		CSimpleMsgDlg(self, msg)
		self.set_skip_taskbar_hint(True)

#---------------------------------------------------------------------------------------------------

	def __doExit(self):
		if self.__oDataHnd.getDoExit() is True:
			sys.exit(self.__oDataHnd.getErrno())

#---------------------------------------------------------------------------------------------------

	def handleException(self, childExcHnd = None):
		'''
		'' Main exception handling function. The function is thread safe.
		'''
		if self.lock.acquire():
			if self.getExcType() is None:
				excHnd = childExcHnd
			else:
				excHnd = self

			#TODO: to be moved to log file
			print 'MAIN handleException() is called by:',excHnd
			print 'MAIN handleException():',excHnd.getExcType(),excHnd.getExcVal(),excHnd.getMsg()

			if excHnd.getExcType() is IOError:
				if excHnd.getExcVal().errno == errno.EACCES:
					self.setDoExit(True)
				elif excHnd.getExcVal().errno == errno.ENOENT:
					gobject.idle_add(self.__showMsg, excHnd.getMsg())
					self.setDoExit(False)
				else:
					self.setDoExit(True)

			if excHnd.getExcType() is CDataHndDBFileNotExistExc:
				gobject.idle_add(self.__showMsg, myconst.MSG_DB_FILE_NOT_EXISTS)
				self.setDoExit(False)
				#self.generateDB()
				#gobject.idle_add(self.generateDB())
				#if excHnd. is CDataHndException.DB_FILE_EXC:
					#self.setDoExit(True)

			if excHnd.getExcType() is CProcMngrStatusException:
				self.setDoExit(True)

			if self.getDoExit() is True:
				exit_text = 'The application will exit due to the following error: ' + excHnd.getMsg()
				gobject.idle_add(self.__showMsg, exit_text)
				gobject.idle_add(gtk.main_quit)	

			excHnd.cleanExc()

			self.lock.release()
		
####################################################################################################
